/**
 *  JFTSTool - Java Full Text Search tool.
 *  Copyright (C) <2009>  <grupo JFTS>
 *  This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 package org.jftstool.fileParserFramework.htmlHandler;

import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.cyberneko.html.parsers.SAXParser;
import org.jftstool.fileParserFramework.IDocumentHandler;
import org.jftstool.fileParserFramework.contentHandler.TeeContentHandler;
import org.jftstool.fileParserFramework.contentHandler.TextContentHandler;
import org.jftstool.fileParserFramework.contentHandler.WriteOutContentHandler;
import org.jftstool.fileParserFramework.contentHandler.XHTMLContentHandler;
import org.jftstool.fileParserFramework.contentHandler.xpath.Matcher;
import org.jftstool.fileParserFramework.contentHandler.xpath.MatchingContentHandler;
import org.jftstool.fileParserFramework.contentHandler.xpath.XPathParser;
import org.jftstool.fileParserFramework.exceptions.DocumentHandlerException;
import org.jftstool.fileParserFramework.metadata.DocumentMetaData;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Parser for HTML documents.
 * @author grupo JFTS
 *
 */
public class HTMLHandler implements IDocumentHandler{
	
    /**
     * Set of safe mappings from incoming HTML elements to outgoing
     * XHTML elements. Ensures that the output is valid XHTML 1.0 Strict.
     */
    private static final Map<String, String> SAFE_ELEMENTS =
        new HashMap<String, String>();

    /**
     * Set of HTML elements whose content will be discarded.
     */
    private static final Set<String> DISCARD_ELEMENTS = new HashSet<String>();

    static {
        // Based on http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd
        SAFE_ELEMENTS.put("P", "p");
        SAFE_ELEMENTS.put("H1", "h1");
        SAFE_ELEMENTS.put("H2", "h2");
        SAFE_ELEMENTS.put("H3", "h3");
        SAFE_ELEMENTS.put("H4", "h4");
        SAFE_ELEMENTS.put("H5", "h5");
        SAFE_ELEMENTS.put("H6", "h6");
        SAFE_ELEMENTS.put("UL", "ul");
        SAFE_ELEMENTS.put("OL", "ol");
        SAFE_ELEMENTS.put("LI", "li");
        SAFE_ELEMENTS.put("DL", "dl");
        SAFE_ELEMENTS.put("DT", "dt");
        SAFE_ELEMENTS.put("DD", "dd");
        SAFE_ELEMENTS.put("PRE", "pre");
        SAFE_ELEMENTS.put("BLOCKQUOTE", "blockquote");
        SAFE_ELEMENTS.put("TABLE", "p"); // TODO colspan/rowspan issues

        DISCARD_ELEMENTS.add("STYLE");
        DISCARD_ELEMENTS.add("SCRIPT");
    }	
	
	
	@Override
	public void getDocument(InputStream is, ContentHandler resultHandler, DocumentMetaData metaData) throws DocumentHandlerException {
		try {
		     // Protect the stream from being closed by CyberNeko
	        //is = new CloseShieldInputStream(is);

	        // Prepare the HTML content handler that generates proper
	        // XHTML events to records relevant document metadata
	        XHTMLContentHandler xhtml = new XHTMLContentHandler(resultHandler, metaData);
	        XPathParser xpath = new XPathParser(null, "");
	        Matcher body = xpath.parse("/HTML/BODY//node()");
	        Matcher title = xpath.parse("/HTML/HEAD/TITLE//node()");
	        Matcher meta = xpath.parse("/HTML/HEAD/META//node()");
	        resultHandler = new TeeContentHandler(
	                new MatchingContentHandler(getBodyHandler(xhtml), body),
	                new MatchingContentHandler(getTitleHandler(metaData), title),
	                new MatchingContentHandler(getMetaHandler(metaData), meta));

	        // Parse the HTML document
	        SAXParser parser = new SAXParser();
	        parser.setContentHandler(new XHTMLDowngradeHandler(resultHandler));
	        //parser.parse(new InputSource(Utils.getUTF8Reader(stream, metaData)));			
	        parser.parse(new InputSource(is));
			
		}
		catch (Exception e) {
			throw new DocumentHandlerException("Error to read HTML document. Can't extract text");			
		}		
	}
	
	
	private ContentHandler getTitleHandler(final DocumentMetaData metadata) {
        return new WriteOutContentHandler() {
            @Override
            public void endElement(String u, String l, String n) {
                metadata.set(DocumentMetaData.TITLE, toString());
            }
        };
    }
	
	private ContentHandler getMetaHandler(final DocumentMetaData metadata) {
        return new WriteOutContentHandler() {
            @Override
            public void startElement(
                    String uri, String local, String name, Attributes atts)
                    throws SAXException {
                    if (atts.getValue("http-equiv") != null) {
                        metadata.set(atts.getValue("http-equiv"), atts.getValue("content"));
                    }
                    if (atts.getValue("name") != null) {
                        metadata.set(atts.getValue("name"), atts.getValue("content"));
                    }
            }
        };
    }
	
    private ContentHandler getBodyHandler(final XHTMLContentHandler xhtml) {
        return new TextContentHandler(xhtml) {

            private int discardLevel = 0;

            @Override
            public void startElement(
                    String uri, String local, String name, Attributes atts)
                    throws SAXException {
                if (discardLevel != 0) {
                    discardLevel++;
                } else if (DISCARD_ELEMENTS.contains(name)) {
                    discardLevel = 1;
                } else if (SAFE_ELEMENTS.containsKey(name)) {
                    xhtml.startElement(SAFE_ELEMENTS.get(name));
                } else if ("A".equals(name)) {
                    String href = atts.getValue("href");
                    if (href == null) {
                        href = "";
                    }
                    xhtml.startElement("a", "href", href);
                }
            }

            @Override
            public void endElement(
                    String uri, String local, String name) throws SAXException {
                if (discardLevel != 0) {
                    discardLevel--;
                } else if (SAFE_ELEMENTS.containsKey(name)) {
                    xhtml.endElement(SAFE_ELEMENTS.get(name));
                } else if ("A".equals(name)) {
                    xhtml.endElement("a");
                }
            }

            @Override
            public void characters(char[] ch, int start, int length)
                    throws SAXException {
                if (discardLevel == 0) {
                    super.characters(ch, start, length);
                }
            }

            @Override
            public void ignorableWhitespace(char[] ch, int start, int length)
                    throws SAXException {
                if (discardLevel == 0) {
                    super.ignorableWhitespace(ch, start, length);
                }
            }

        };
    }	
}